<?php
/**
 * This file is part of Hyperf.
 *
 * @link     https://www.hyperf.io
 * @document https://hyperf.wiki
 * @contact  group@hyperf.io
 * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
 */
namespace App\Controller;

use GuzzleHttp\Exception\GuzzleException;
use Hyperf\HttpServer\Annotation\Controller;
use Hyperf\HttpServer\Annotation\RequestMapping;
use Hyperf\Utils\ApplicationContext;
use App\Service\UserService;
use Hyperf\Utils\Coroutine\Concurrent;
use App\Validate\UserValidate;

/**
 * 用户
 * @Controller(prefix="home/user")
 */
class UserController extends AbstractController {

    /**
     * 登录
     * @author yongwei
     * @RequestMapping(path="login")
     */
    public function login() {
        $params['username'] = $this->request->input('username');
        $params['password'] = $this->request->input('password');
        $service = new UserService();
        $user = $service->login($params);

        return [
            'method' => $this->request->getMethod(),
            'action' => $this->request->getRequestUri(),
            'params' => $this->request->all(),
        ];
    }

    /**
     * 用户信息
     * @author yongwei
     * @RequestMapping(path="user-info")
     */
    public function userInfo()
    {
        $service = new UserService();
        $data = $service->userInfo();
        return $this->success($data);
    }

    /**
     * 协程测试1
     * @author yongwei
     * @RequestMapping(path="xie-cheng")
     */
    public function xiecheng()
    {
        //普通线程是抢占式的，哪个线程能得到资源由操作系统决定，而协程是协作式的
        //通俗来讲，并行阻塞的传统方式是要等水烧开了才能进行下一步刷牙的操作，刷完牙了才能进行吃早餐的操作
        //看到上面的执行顺序,你可能还是不能理解协程的优点,这个其实是一个很简单的概念,举个例子:
        //小明烧开水需要10分钟,刷牙需要3分钟,吃早餐需要5分钟,请问做完这些事情总共需要多少分钟?
        //答案是10分钟,因为在烧开水这个步骤时,不需要坐在那里看水壶烧(异步,io耗时)可以先去刷牙,然后去吃早餐
        //协程的优点主要在于这里,当遇上io耗时的情况时,这部分的等待时间我们其实可以节约出来,去先处理其他代码逻辑的,直到io完成再继续执行之前的代码.
        //没错,协程的优点就在于这个.

        //协程的运行是交叉式运行(串行),只要你发起了一次协程切换,则会立马暂停当前协程,去运行下一个协程,直到下次代码调度回协程.
        //由于协程没有了io耗时,执行速度大大提高,假设请求一次网站需要0.05秒,那500次循环就相当于节省了25秒,这就是为什么协程适合在高并发io场景的原因

        //由这2个流程可以看出一个不同之处:非协程需要等待请求网页的时间,而协程直接跳过了等待的时间,继续往下执行,
        //也就是上面说的"小明烧开水的时间先去刷牙"
        //然后,由于协程没有了io耗时,执行速度大大提高,
        //假设请求一次网站需要0.05秒,那500次循环就相当于节省了25秒,这就是为什么协程适合在高并发io场景的原因了

        //可以通过 Hyperf\Utils\Coroutine::id(): int 获得当前的 协程 ID，如不处于协程环境下，会返回 -1

        //Channel通道 主要用于协程间通讯，当我们希望从一个协程里返回一些数据到另一个协程时，就可通过 Channel 来进行传递

        //Hyperf\Utils\Coroutine\Concurrent 基于 Swoole\Coroutine\Channel 实现，用来控制一个代码块内同时运行的最大协程数量
        //当同时执行 5 个子协程时
        $concurrent = new Concurrent(5);

        for ($i = 1; $i < 10; ++$i) {
            $concurrent->create(function () use ($i) {
                \Swoole\Coroutine::sleep(1); //模拟IO阻塞，协程会自动调度切换
                $id = \Hyperf\Utils\Coroutine::id();
                var_dump("i-$i"." id-".$id);
                //string(8) "i-1 id-3"
                //string(8) "i-2 id-4"
                //string(8) "i-4 id-6"
                //string(8) "i-5 id-7"
                //string(8) "i-3 id-5"
                //string(8) "i-7 id-9"
                //string(8) "i-6 id-8"
                //string(9) "i-9 id-11"
                //string(9) "i-8 id-10"
            });
        }
        return [];
    }

    /**
     * 下载图片数据
     * @author yongwei
     * @RequestMapping(path="crawlin-data")
     */
    public function crawlingData()
    {
        $params = $this->request->all();
        if (empty($params['url'])) {
            abort(0,'链接不能为空');
        }

        $client = new \GuzzleHttp\Client();
        try {
            $resp = $client->request('GET', $params['url']);
            $body = $resp->getBody()->getContents();
            $body = json_decode($body, true);
            //var_dump($body);die;
        } catch (\Exception $ex) {
            abort(0, $ex->getMessage());
        } catch (GuzzleException $e) {
            abort(0, $e->getMessage());
        }

        $concurrent = new Concurrent(10);
        for ($i = 1; $i < 10; ++$i) {
            $concurrent->create(function () use ($i, $body) {
                $id = \Hyperf\Utils\Coroutine::id();
                var_dump("i-$i"." id-".$id);

                foreach ($body['data'] as $key => $val) {
                    foreach ($val['result'] as $k => $v) {
                        if (!empty($v['card_uid']) && !empty($v['big_image'])) {
                            $this->saveImage($v['card_uid'], $v['big_image']);
                        }
                    }
                }
            });
        }
        return ['msg' => 'ok'];
    }

    /**
     * 从网上下载图片保存到服务器
     * @param $url
     * @return string
     */
    public function saveImage($cardCode, $url)
    {
        $filename = $cardCode.'.png';//文件名称生成
        //$filename = date("YmdHis").Str::random().'.png';//文件名称生成
        //$mkdir = date('Y').'/'.date('m').'/'.date('d').'/'; // 返回路径
        $path = BASE_PATH . '/runtime/cardimg/'; // 存储路径
        $ch = curl_init ();
        curl_setopt ( $ch, CURLOPT_CUSTOMREQUEST, 'GET' );
        curl_setopt ( $ch, CURLOPT_SSL_VERIFYPEER, false );//跳过ssl验证
        curl_setopt ( $ch, CURLOPT_URL, $url );
        ob_start ();
        curl_exec ( $ch );
        $return_content = ob_get_contents ();
        ob_end_clean ();
        $fp= fopen($path.$filename,"a");
        fwrite($fp,$return_content); //写入文件
        fclose($fp);
        return $path.$filename;
    }

    /**
     * 协程测试2
     * @author yongwei
     * @RequestMapping(path="xie-cheng-two")
     */
    public function xiechengTwo()
    {
        co(function () {
            echo "heelo go1\n";
        });

        echo "hello main\n";

        co(function () {
           echo "heelo go2\n";
        });
        return [];
        //heelo go1
        //hello main
        //heelo go2

        //执行过程:
        //运行此段代码, 系统启动一个新进程
        //遇到 go(), 当前进程中生成一个协程, 协程中输出 heelo go1, 协程退出
        //进程继续向下执行代码, 输出 hello main
        //再生成一个协程, 协程中输出 heelo go2, 协程退出
    }

    /**
     * 协程测试3
     *  协程适合 IO密集型 的应用
     * @author yongwei
     * @RequestMapping(path="xie-cheng-three")
     */
    public function xiechengThree()
    {
        //sleep() 可以看做是 CPU密集型任务, 不会引起协程的调度
        //Co::sleep() 模拟的是 IO密集型任务, 会引发协程的调度

        co(function () {
            \Swoole\Coroutine::sleep(1);
            //$id = \Hyperf\Utils\Coroutine::id();
            echo "heelo go1\n";
        });

        echo "hello main\n";

        co(function () {
            //$id = \Hyperf\Utils\Coroutine::id();
            echo "heelo go2\n";
        });
        return [];
        //hello main
        //heelo go2
        //heelo go1

        //实际执行过程:
        //运行此段代码, 系统启动一个新进程
        //遇到 go(), 当前进程中生成一个协程
        //协程中遇到 IO阻塞 (这里是 Co::sleep() 模拟出的 IO等待), 协程让出控制, 进入协程调度队列
        //进程继续向下执行, 输出 hello main
        //执行下一个协程, 输出 hello go2
        //之前的协程准备就绪, 继续执行, 输出 hello go1
    }

    /**
     * 验证器测试
     * @author yongwei
     * @RequestMapping(path="validator")
     */
    public function validator()
    {
        $params['username'] = $this->request->input('username');
        $params['password'] = $this->request->input('password');
        $msg = make(UserValidate::class)->check($params, 'add');
        if ($msg) {
            return $this->error($msg);
        }
        return $this->success($params);
    }

}
